﻿//-----------------------------------------------------------------------
// <copyright file="SQLiteCommand.SQLiteDataReader.cs" company="NextLine Software">
//     Copyright (c) NextLine Software. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace NextLine.Data.SQLite
{
    using System;
    using System.Collections.Generic;
    using System.Data;
    using NextLine.Data.SQLite.Wrapper;

    public partial class SQLiteCommand
    {
        private class SQLiteDataReader : IDataReader
        {
            private readonly SQLiteCommand command;
            private IDictionary<string, int> columnNamesToIndex;
            private IList<ColumnInfo> columns;

            public SQLiteDataReader(SQLiteCommand command)
            {
                this.command = command;
            }

            public int Depth
            {
                get
                {
                    return 0;
                }
            }

            public bool IsClosed
            {
                get;
                private set;
            }

            public int RecordsAffected
            {
                get { throw new NotImplementedException(); }
            }

            public int FieldCount
            {
                get
                {
                    return this.columns.Count;
                }
            }

            public object this[string name]
            {
                get
                {
                    int ordinal = this.GetOrdinal(name);
                    return this.GetColumnValue<object>(ordinal);
                }
            }

            public object this[int i]
            {
                get
                {
                    return this.GetColumnValue<object>(i);
                }
            }

            public void Close()
            {
                if (!this.IsClosed)
                {
                    this.IsClosed = true;
                }
            }

            public bool NextResult()
            {
                throw new NotImplementedException();
            }

            public bool Read()
            {
                Sqlite3.Vdbe statement;
                if (this.columns == null)
                {
                    // read the column names
                    this.command.Prepare();
                    this.command.BindParameters();

                    statement = this.command.preparedCommand;
                    bool returnValue = Sqlite3.sqlite3_step(statement) == Sqlite3.SQLITE_ROW;
                    var columnCount = Sqlite3.sqlite3_column_count(statement);

                    var columnInformation = new List<ColumnInfo>(columnCount);
                    var columnNames = new Dictionary<string, int>(columnCount, StringComparer.OrdinalIgnoreCase);

                    for (int i = 0; i < columnCount; i++)
                    {
                        var column = new ColumnInfo()
                        {
                            ColumnName = Sqlite3.sqlite3_column_name(statement, i),
                            ColumnType = Sqlite3.sqlite3_column_type(statement, i)
                        };

                        columnInformation.Add(column);
                        columnNames.Add(column.ColumnName, i);
                    }

                    this.columns = columnInformation;
                    this.columnNamesToIndex = columnNames;

                    return returnValue;
                }
                else
                {
                    return Sqlite3.sqlite3_step(this.command.preparedCommand) == Sqlite3.SQLITE_ROW;
                }
            }

            public void Dispose()
            {
                this.Close();
            }

            public bool GetBoolean(int i)
            {
                return this.GetColumnValue<bool>(i);
            }

            public byte GetByte(int i)
            {
                return this.GetColumnValue<byte>(i);
            }

            public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
            {
                throw new NotImplementedException();
            }

            public char GetChar(int i)
            {
                return this.GetColumnValue<char>(i);
            }

            public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
            {
                throw new NotImplementedException();
            }

            public IDataReader GetData(int i)
            {
                throw new NotImplementedException();
            }

            public string GetDataTypeName(int i)
            {
                throw new NotImplementedException();
            }

            public DateTime GetDateTime(int i)
            {
                throw new NotImplementedException();
            }

            public decimal GetDecimal(int i)
            {
                return this.GetColumnValue<decimal>(i);
            }

            public double GetDouble(int i)
            {
                return this.GetColumnValue<double>(i);
            }

            public Type GetFieldType(int i)
            {
                throw new NotImplementedException();
            }

            public float GetFloat(int i)
            {
                return this.GetColumnValue<float>(i);
            }

            public Guid GetGuid(int i)
            {
                throw new NotImplementedException();
            }

            public short GetInt16(int i)
            {
                return this.GetColumnValue<short>(i);
            }

            public int GetInt32(int i)
            {
                return this.GetColumnValue<int>(i);
            }

            public long GetInt64(int i)
            {
                return this.GetColumnValue<long>(i);
            }

            public string GetName(int i)
            {
                if (i < 0 || i > this.columns.Count)
                {
                    throw new IndexOutOfRangeException();
                }

                return this.columns[i].ColumnName;
            }

            public int GetOrdinal(string name)
            {
                int ordinal;
                if (!this.columnNamesToIndex.TryGetValue(name, out ordinal))
                {
                    throw new IndexOutOfRangeException();
                }

                return ordinal;
            }

            public string GetString(int i)
            {
                return this.GetColumnValue<string>(i);
            }

            public object GetValue(int i)
            {
                return this.GetColumnValue<object>(i);
            }

            public int GetValues(object[] values)
            {
                throw new NotImplementedException();
            }

            public bool IsDBNull(int i)
            {
                throw new NotImplementedException();
            }

            private T GetColumnValue<T>(int index)
            {
                if (index < 0 || index > this.columns.Count)
                {
                    throw new IndexOutOfRangeException();
                }

                // validate type
                Type clrType = typeof(T);

                if (clrType != typeof(object))
                {
                    int parameterType = sqliteTypeMapping[clrType];
                    if (parameterType != this.columns[index].ColumnType)
                    {
                        throw new InvalidOperationException("Invalid type for column " + index + " (" + this.columns[index].ColumnName + ")");
                    }
                }

                switch (this.columns[index].ColumnType)
                {
                    case Sqlite3.SQLITE_INTEGER:
                        return (T)Convert.ChangeType(Sqlite3.sqlite3_column_int64(this.command.preparedCommand, index), clrType, System.Threading.Thread.CurrentThread.CurrentCulture);

                    case Sqlite3.SQLITE_FLOAT:
                        return (T)Convert.ChangeType(Sqlite3.sqlite3_column_double(this.command.preparedCommand, index), clrType, System.Threading.Thread.CurrentThread.CurrentCulture);

                    case Sqlite3.SQLITE_BLOB:
                        return (T)Convert.ChangeType(Sqlite3.sqlite3_column_blob(this.command.preparedCommand, index), clrType, System.Threading.Thread.CurrentThread.CurrentCulture);

                    case Sqlite3.SQLITE_TEXT:
                        return (T)Convert.ChangeType(Sqlite3.sqlite3_column_text(this.command.preparedCommand, index), clrType, System.Threading.Thread.CurrentThread.CurrentCulture);

                    case Sqlite3.SQLITE_NULL:
                        return default(T);
                }

                throw new InvalidOperationException("Could not read value for column " + index);
            }

            private struct ColumnInfo
            {
                public int ColumnType;
                public string ColumnName;
            }
        }
    }
}